#  Public and for release, CBEC v3.2.06 Build 4255+ [14th November, 2025]
#
#  Copyright(c) Ivyware Pty Ltd 2021-24  (all rights reserved)
#               MELBOURNE, VICTORIA, AUSTRALIA, 3000
#
#  This file is provided as-is by Ivyware Pty Ltd.  No claims are made
#  as to fitness for any particular purpose.  No warranties of any kind
#  are expressed or implied.  The recipient agrees to determine
#  applicability of information provided.
#
#  Ivyware hereby grants the right to freely use the information
#  supplied in this file for the creation of Python automation scripts
#  supporting the Chartboard Application, and to make copies of this
#  file in any form for internal or external distribution as long as
#  this notice remains attached.
#
#  No waranty or suitability for purpose is implied.
#
#  Simple Python Scanner script using fitted Harmonics object to generate
#  a list of stocks with bullish buy signals.
#  NOTES: Download latest version from
#         https://www.ivyware.com.au/PythonScripts/SampleScanner[Harmonics].pys
#       : Download latest version of the Chartboard Extension Classes
#         (PythonCBEC.pyw) upon which this script is based from
#         https://www.ivyware.com.au/PythonScripts/PythonCBEC.pyw
#       : Script interacts with desktop ribbon bar via selected values for
#         the "Period Units" and "Lookback Periods".
#       : Requirement is for phython 3.8 to be installed
#       : Based upon Chartboard Extension Classes (CBEC) shipped with
#         Chartboard product.  Script runs as scanning client of Chartboard.
#       : Script is run once for each stock encountered by the scanner,
#         state is not maintained between runs.  Selection status MUST
#         be confirmed for positive scans
#       : Scanning scripts are allocated the *.pys extension, advisor scripts
#         the *.pya extenstion and common scripts the *.pyw extension.
#

import sys
sys.path.insert(0, 'C:\\Program Files\\Chartboard\\PythonScripts') # CBEC directory
from PythonCBEC import *
import ctypes  # An included library with Python install.   
from datetime import datetime
from PYCB import PYCB_return
from PYCB import PYCB_error
#
#  Environment variables
#  NOTES: List of current environment variables suitable for debugging
import os
#print ( 'Python environment')
#for param in os.environ.keys():
#    print ( "%20s %s" % (param,os.environ[param]) )
#
#   STEP 1 - Establish Root from which all other objects are descendants
#   NOTES: Effectively the Chartboard application itself.  This script
#          is coded to run on CScanOHLCvs type views only.
#        : The parent window 'CView' refers to collection of tabbed views
#          in the centre of the application, 'this' refers to current
#          Chartboard view with focus from which script has been activated.
oCRoot = CRoot();
#
#   STEP 2 - Establish CView
#   NOTES: CView is the parent of all the tabbed view types. Effectively
#          the View tab under which this python script is running and
#          identified by the 'this' tag
#        : Confirm 'CScanOHLCvs' type view, script only supports such
oCView = oCRoot.CViewFactory('this');
if not oCView.IsCScanOHLCvs():
    raise PYCB_error ( "Script only supports CSanOHLCvs type CView's, not " + oCView.sType )
#
#   STEP 3 - Establish the CScanOHLCvs object
#   NOTES: Must be a scan type View tab for this script to work.  Fails for
#          all other view types.  Script may only access single scan tab.
#        : Scan Period Units controlled by the current Ribbon Bar >> Scan Group
#          >> Scan Period Units tab selection.  Alternatively this value may be
#          over-ridden via PUnits_Year, Quarter, Month, Week or Day definition
#          from PythonCBEC.pyw
#        : Calculations are performed and values persist concurrently for all
#          Period Units.  Hence, state for alternative periods can be compared.
oCScanOHLCvs = oCView.CScanFactory();
#oCScanOHLCvs.Setenvar_i('PUnits', PUNITS_Month )
#oCScanOHLCvs.Setenvar_i('LBPeriods', 8 )
oCScanOHLCvs.Refresh()                    # Refreshes cached values
#
#   STEP 4 - Access the CStackOHLCvs object within oCScanOHLCvs
#   NOTES: The ScanOHLCvs tab builds a synthetic chart for encountered
#          stocks containing all charts and overlays referenced as
#          Prerequisites in this script.
#        : Chart reference order is respected.  Chart can be viewed
#          through context menu of selected stocks list control in the
#          ScanView
#        : Some of the Prerequisites are for demonstration and cosmetic
#          purposes only.  Firstly the chart stack is defined as a list of
#          prerequisites.
oCStackOHLCvs = oCScanOHLCvs.CStackFactory()
oCStackOHLCvs.Prerequisites('RSI')
oCStackOHLCvs.Prerequisites('OHLCvs')
oCStackOHLCvs.Prerequisites('MACD')
oCStackOHLCvs.Prerequisites('MSA')
oCScanOHLCvs.SetSelected ( 0 )         # Not-selected as default summary
#
#   STEP 5 - Configuration of Charts and Overlays within stack
#   NOTES: Chart decorations that may or may not be referenced by scan logic
#        : Secondly the Prerequisite overlays are defined for each chart.
if oCStackOHLCvs.ChartExists('RSI'):
    oChartRSI = oCStackOHLCvs.ChartFactory('RSI')
    oChartRSI.Prerequisites('RSI')
    oDSeriesRSI = oChartRSI.DSeriesFactory('RSI')
    # Overlay Signaline
    oDSeriesRSI.SetConfig_i('Signaline',1)
    oDSeriesRSI.SetConfig_i('SignalineSMA',1)
if oCStackOHLCvs.ChartExists('OHLCvs'):
    oChartOHLCvs = oCStackOHLCvs.ChartFactory('OHLCvs')
    # Overlay Reversals 
    oChartOHLCvs.Prerequisites('Reversals-A')
    oDSeriesReversals = oChartOHLCvs.DSeriesFactory('Reversals-A')
    oDSeriesReversals.SetParam_i('RPMask-BULL',RPMask_BULLs)
    oDSeriesReversals.SetParam_i('RPMask-BEAR',RPMask_BEARs)
    # Overlay Harmonics
    oChartOHLCvs.Prerequisites('Harmonics-A')
if oCStackOHLCvs.ChartExists('MACD'):
    oChartMACD = oCStackOHLCvs.ChartFactory('MACD')
    oChartMACD.Prerequisites('MACD')
    oDSeriesMACD = oChartMACD.DSeriesFactory('MACD')
    # Overlay Buy-Sell signals
    oDSeriesMACD.SetConfig_i('SignalBUY',1)
    oDSeriesMACD.SetConfig_i('SignalSELL',1)
if oCStackOHLCvs.ChartExists('MSA'):
    oChartMSA = oCStackOHLCvs.ChartFactory('MSA')
    oChartMSA.Prerequisites('MSA')
    oDSeriesMSA = oChartMSA.DSeriesFactory('MSA')
    # Overlay Signaline
    oDSeriesMSA.SetConfig_i('Signaline',1)
    oDSeriesMSA.SetConfig_i('SignalineSMA',1)

#   STEP 6 - Position cursor at reference date
#   NOTES: Reference date is controlled by the Ribbon Bar >> Scan Group
#          >> Reference Date selection.  Alternatively this value may be
#          over-ridden via '[x] RefDate Today'
nPUnits      = oCScanOHLCvs.nPUnits
dRefDateSet  = oCScanOHLCvs.SetCursorPos(oCScanOHLCvs.dRefDATE,nPUnits)
nDATEmin     = oCScanOHLCvs.GetDSetDATE('DATEmin',nPUnits)
nDATEmax     = oCScanOHLCvs.GetDSetDATE('DATEmax',nPUnits)
nRefDateDiff = P2Helpers.P2PUnits_Diff ( oCScanOHLCvs.dRefDATE, nDATEmin, nPUnits )
if nRefDateDiff <= oCScanOHLCvs.nLBPeriods :
    print ( 'No data exists within loop back periods=' + str(nRefDateDiff) )
    raise PYCB_return
print ( '-----------------------' )
dRefDATEset = oCScanOHLCvs.SetCursorPos(oCScanOHLCvs.dRefDATE,nPUnits)
nCursorPos  = oCScanOHLCvs.GetCursorPos(nPUnits)
dDiff       = P2Helpers.P2PUnits_Diff ( dRefDATEset, nCursorPos, nPUnits )

#################################################
#
#   Harmonic signals
#   NOTES: Select only those with nominated signals, then apply environmental logic
#        : dMACD - Calculated MACD value
#          nBoS  - Buy(true) or Sell(false) state
#          nBoSage - Age of buy/sell state in lookback periods
while oCScanOHLCvs.ChartExists('OHLCvs'):
    oChartOHLCvs = oCStackOHLCvs.ChartFactory('OHLCvs')
    oChartOHLCvs.Prerequisites('Harmonics-A')
    oDSeriesHarmonicsA = oChartOHLCvs.DSeriesFactory ('Harmonics-A')
    #oCScanOHLCvs.FastForward()
    oHarmonicOb = oDSeriesHarmonicsA.HarmonicobFactory(oCScanOHLCvs.nPUnits,0,'Last')
    if ( oHarmonicOb.IsEmpty() == True):
        break
    dMACD       = oDSeriesMACD.GetValue_d('MACD',oCScanOHLCvs.nPUnits,0)
    nBoS        = oDSeriesMACD.GetValue_i('BoS',oCScanOHLCvs.nPUnits,0)
    nBoSage     = oDSeriesMACD.GetValue_i('BoSage',oCScanOHLCvs.nPUnits,0)
    nAgeD       = oHarmonicOb.GetValue_i ('AgeD')
    nHarmonicType  = 0
    if ( nAgeD is None or nAgeD > nLBPeriods ):
      break
    nHarmonicType = oHarmonicOb.GetValue_i('Type')
    #if ( (nHarmonicType&RPType_BULL_ENGULFING) == RPType_BULL_ENGULFING ):
    #    oCScanOHLCvs.SetSelected ( True )
    #if ( (nHarmonicType&RPType_BULL_HAMMER) == RPType_BULL_HAMMER ):
    #    oCScanOHLCvs.SetSelected ( True )
    #if ( (nHarmonicType&RPType_BULL_HARAMI) == RPType_BULL_HARAMI ):
    #    oCScanOHLCvs.SetSelected ( True )
    if ( oHarmonicOb.GetValue_b('IsBullish') == False ):
        break;
    if ( oHarmonicOb.IsEmpty() == False ):
        sSummary = oHarmonicOb.GetValue_s('Type') + '(Bull)' + '(' + str(nAgeD) + ')'
        oDSeriesHarmonicsA.Setenvar_s('Summary',sSummary )
        oCScanOHLCvs.SetSelected ( True ) # Flags this stock/chart as selected
    if ( nHarmonicType ):
        sComments = 'MACD < 0; MACD buy signal <= ' + str(oCScanOHLCvs.nLBPeriods) + oCScanOHLCvs.sPUnits
        oCScanOHLCvs.Setenvar_s('Comments', sComments )

    raise PYCB_return




